//OpenCollar - inplaceUpdater - 3.400
//Licensed under the GPLv2, with the additional requirement that these scripts remain "full perms" in Second Life.  See "OpenCollar License" for details.

// First generation updater for the SubAO and Owner Hud.  
// Plans for this script: (3.5) -> Merge with collar updates from issue 760

//in place updater

//updaters should always end with "Updater" !!!!!
string ownerUpdater = "OC Owner Hud Updater";
string subAOUpdater  = "OC Sub AO Updater";
string collarUpdater  = "OpenCollar Updater";

//select product
string product;

// the minimum version the object needs to have for the update to work, usually due to changes in the prims
float g_fMinimumSubAOVersion = 3.400; 
float g_fMinimumOwnerVersion = 3.400;
float g_fMinimumCollarVersion = 3.020; 

string g_szReplacementItemToRequest;
string baseurl = "http://collardata.appspot.com/updater/check?";

string what2update;
float minVersion;
string clickUpdate;
integer g_nDoubleCheckChannel=-0x10CC011B;
integer updateChannel;

integer listenhandle;
float version;
list offering;
integer updatePin;
key myKey;

string hoverText = "PLEASE WAIT\n";

integer line;
key dataid;
string noteCard = "OldItemsToDelete";
list oldItemsToDelete;

string instructions;

key particletexture = "41873cb5-cb92-abca-16c4-ac319c9e2067";

integer debughandle;
string debugMessage;

key httprequest;

key g_keyWearer; // id of wearer
key g_keyObject;

debug(string str)
{
    //llOwnerSay(llGetScriptName() + ": " + str);
}

checkUpdaterType()
{
    what2update = llList2String(llParseString2List(llGetObjectName(), [" - "], []), 0);
    
    if (llSubStringIndex(what2update,ownerUpdater) == 0)
    {
        updateChannel = -7483212;
        g_nDoubleCheckChannel=-0x10CC011C;
        minVersion = g_fMinimumOwnerVersion;
        g_szReplacementItemToRequest ="OpenCollar Owner Hud";
        clickUpdate = "Touch the Owner Hud and select Update";
    }
    else if (llSubStringIndex(what2update,subAOUpdater) == 0)
    {
        updateChannel = -7483211;
        g_nDoubleCheckChannel=-0x10CC011B;
        minVersion = g_fMinimumSubAOVersion;
        g_szReplacementItemToRequest ="OpenCollar Sub AO";
        clickUpdate = "Touch the SubAO and select Update";
    }
    else if (llSubStringIndex(what2update,collarUpdater) == 0)
    {
        updateChannel = -7483210;
        g_nDoubleCheckChannel=-0x10CC011A;
        minVersion = g_fMinimumCollarVersion;
        g_szReplacementItemToRequest ="OpenCollar";
        clickUpdate = "Touch the collar and select Help/Debug->Update";
    }     
}

integer isUpdateManagerScript(string name)
{
    name = llList2String(llParseString2List(name, [" - "], []), 1);
    if (name == "updateManager")
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

UpdateParticle(key target, vector color)
{
    integer effectFlags;
    effectFlags = effectFlags|PSYS_PART_INTERP_COLOR_MASK;
    effectFlags = effectFlags|PSYS_PART_INTERP_SCALE_MASK;
    effectFlags = effectFlags|PSYS_PART_FOLLOW_SRC_MASK;
    effectFlags = effectFlags|PSYS_PART_FOLLOW_VELOCITY_MASK;
    effectFlags = effectFlags|PSYS_PART_TARGET_POS_MASK;
    effectFlags = effectFlags|PSYS_PART_EMISSIVE_MASK;
    llParticleSystem([
        PSYS_PART_FLAGS,            effectFlags,
        PSYS_SRC_PATTERN,           PSYS_SRC_PATTERN_DROP,
        PSYS_PART_START_COLOR,      color,
        PSYS_PART_END_COLOR,        color,
        PSYS_PART_START_ALPHA,      0.75,
        PSYS_PART_END_ALPHA,        0.25,
        PSYS_PART_START_SCALE,      <0.25,0.25,0.0>,
        PSYS_PART_END_SCALE,        <0.04,0.04,0.0>,
        PSYS_PART_MAX_AGE,          2.0,
        PSYS_SRC_ACCEL,             <0.0,0.0,0.0>,
        PSYS_SRC_TEXTURE,           particletexture,
        PSYS_SRC_BURST_RATE,        0.1,
        PSYS_SRC_INNERANGLE,        0.0,
        PSYS_SRC_OUTERANGLE,        0.0,
        PSYS_SRC_BURST_PART_COUNT,  1,
        PSYS_SRC_BURST_RADIUS,      0.0,
        PSYS_SRC_BURST_SPEED_MIN,   1.0,
        PSYS_SRC_BURST_SPEED_MAX,   2.0,
        PSYS_SRC_MAX_AGE,           0.0,
        PSYS_SRC_TARGET_KEY,        target,
        PSYS_SRC_OMEGA,             <0.0, 0.0, 0.0>   ]);
}

initiate()
{
    llSetColor(<0,0,0>, 0);
    line = 0;
    oldItemsToDelete = [];
    dataid = llGetNotecardLine(noteCard, line);
    llParticleSystem([]);
    llSetLinkColor(3, <1,0,0>, ALL_SIDES);
    //create a list of inventory types the updater has to offer the collar, store the updater version and open the listener
    integer n;
    list types = [INVENTORY_SCRIPT, INVENTORY_OBJECT, INVENTORY_NOTECARD, INVENTORY_TEXTURE, INVENTORY_ANIMATION, INVENTORY_SOUND];
    for (n = 0; n < llGetListLength(types); n++)
    {
        integer type = llList2Integer(types, n);
        if (llGetInventoryNumber(type) > 0)
        {
            if (type == INVENTORY_SCRIPT || type == INVENTORY_NOTECARD)
            {
                if (llGetInventoryNumber(type) > 1)
                {
                    offering += [type];
                }
            }
            else
            {
                offering += [type];
            }
        }
    }

    UnRunScripts();
    version = (float)llList2String(llParseString2List(llGetObjectDesc(), ["~"], []), 1);
    //here important: the name of this object ends with "Updater" !!!!
    instructions = what2update + " Update - " + llGetSubString((string)version, 0, 4);
    instructions += "\nTo update your " + g_szReplacementItemToRequest + " (version " + llGetSubString((string)minVersion, 0, 4) + " or later):";
    instructions += "\n1 - Rez it next to me.";
    instructions += "\n2 - " + clickUpdate;
    llSetText(instructions, <1,1,1>, 1);
    myKey = llGetKey();
    listenhandle = llListen(updateChannel, "", "", "");
    debughandle = llListen(DEBUG_CHANNEL, "", "", "");
    llOwnerSay(instructions);
}

UnRunScripts()
{
    // set all scripts in me to NOT RUNNING
    integer n;
    for (n = 0; n < llGetInventoryNumber(INVENTORY_SCRIPT); n++)
    {
        string script = llGetInventoryName(INVENTORY_SCRIPT, n);
        if (script != llGetScriptName())
        {
            if (llGetInventoryType(script) == INVENTORY_SCRIPT)
            {
                if(llGetScriptState(script))
                {
                    llSetScriptState(script, FALSE);
                }
            }
            else
            {
                //somehow we got passed a script we can't find.  Wait a sec and try again
                if (llGetInventoryType(script) == INVENTORY_SCRIPT)
                {
                    llSetScriptState(script, FALSE);
                }
                else
                {
                    llWhisper(DEBUG_CHANNEL, "Could not set " + script + " to not running.");
                }
            }
        }
    }
}

OfferUpdate(key id)
{
    UpdateParticle(id, <1,0,0>);
    llSetLinkColor(3, <1,0,1>, ALL_SIDES);
    llSetText(hoverText + "Preparing Update", <1,1,0>, 1);
    llDialog(g_keyWearer,"Update started, please wait until it finished completely.\nThis will take a minute or two...", ["Ok"],-47114711);
    string message = "toupdate," + llDumpList2String(offering, ",");
    llWhisper(updateChannel, message);
}

GiveItemList(integer type)
{
    integer i;
    list items;
    for( i = 0; i < llGetInventoryNumber(type); i++)
    {
        if (type == INVENTORY_SCRIPT)
        {
            if (llGetInventoryName(type, i) != llGetScriptName())
            {
                items += llGetInventoryName(type, i);
            }
        }
        else if (type == INVENTORY_NOTECARD)
        {
            if (llGetInventoryName(type, i) != noteCard)
            {
                items += llGetInventoryName(type, i);
            }
        }
        else
        {
            items += llGetInventoryName(type, i);
        }
    }
    string myItems = llDumpList2String(items, ",");
    llWhisper(updateChannel, "items," + (string)type + "," + myItems);
}

CopyUpdateManager(key id, integer updatePin)
{
    string name;
    integer n;
    debug("sending updateManager script");
    for (n = 0; n < llGetInventoryNumber(INVENTORY_SCRIPT); n++)
    {
        name = llGetInventoryName(INVENTORY_SCRIPT, n);
        if(isUpdateManagerScript(name))
        {
            hoverText += ".";
            llSetText(hoverText + "\n" + name, <1,0,0>, 1);
            llRemoteLoadScriptPin(id, name, updatePin, TRUE, 42);
        }
    }
}

StartUpdate(key id, integer update)
{
    UpdateParticle(id, <1,1,1>);
    integer i;
    hoverText += "Updating Items";
    llSetLinkColor(3, <0,0,1>, ALL_SIDES);
    llSetText(hoverText, <1,0,0>, 1);
    string name;
    debug("sending non script items");
    for (i = 0; i < llGetListLength(offering); i++)
    {
        integer type = llList2Integer(offering, i);
        if (type != INVENTORY_SCRIPT)
        {
            integer n;
            for(n = 0; n < llGetInventoryNumber(type); n++)
            {
                name = llGetInventoryName(type, n);
                if(name != noteCard)
                {
                    hoverText += ".";
                    llSetText(hoverText + "\n" + name, <1,0,0>, 1);
                    llGiveInventory(id, name);
                }
            }
        }
    }
    debug("sending scripts");
    for (i = 0; i < llGetInventoryNumber(INVENTORY_SCRIPT); i++)
    {
        name = llGetInventoryName(INVENTORY_SCRIPT, i);
        if (name != llGetScriptName() && !isUpdateManagerScript(name))
        {
            hoverText += ".";
            llSetText(hoverText + "\n" + name, <1,0,0>, 1);
            llRemoteLoadScriptPin(id, name, update, FALSE, 42);
        }
    }

    debug("done sending scripts, sending version command");
    //added a lil pause to let the update script deal with LMs
    llSleep(2.0);
    llWhisper(updateChannel, "version," + (string)version);
    UpdateParticle(id, <1,0,0>);
    llSetText("PLEASE WAIT\nFinalizing Update", <1,0,0>, 1);
}

default
    //state to eleminate multiple updaters
{
    state_entry()
    {
        g_keyWearer = llGetOwner();
        g_keyObject = llGetKey();
        // init the double updater search
        llSetText("Updater is initalizing. Please wait ...",<1,0,0>,1.0);
        checkUpdaterType();
        // listen on a channel
        llListen(g_nDoubleCheckChannel,"",NULL_KEY,"");
        // and say on the smae channel we are here
        llSay(g_nDoubleCheckChannel,"UpdateCheck:"+(string)g_keyObject);
        // doublecheck with a sensor in casse lags eats us, listener i to avoid to many object near
        llSensor("", NULL_KEY, SCRIPTED, 15, PI);
        llSetTimerEvent(2.0);
    }

    on_rez(integer start_param)
    {
        llResetScript();
    }
    changed(integer change)
    {
        if(change & CHANGED_INVENTORY)
        {
            llResetScript();
        }
    }

    listen(integer channel, string name, key id, string message)
    {
        if ((channel == g_nDoubleCheckChannel) && (llGetOwnerKey(id) == g_keyWearer))
        {// we received our owner message back
            if (message=="UpdateCheck:"+(string)g_keyObject)
            {// so we delete ourself
                llOwnerSay("There was more than one " + what2update + " Updater rezzed from you. So this one got deleted. Please use only one updater.");
                llDie();
            }
            else
            {
                llSay(g_nDoubleCheckChannel,message);
            }
        }
    }

    sensor(integer num_detected)
    { // our sensor check, we check all object that are scripted
        integer i;
        for (i = 0; i < num_detected; i++)
        {
            if ((llDetectedKey(i) != g_keyObject) && (llGetOwnerKey(llDetectedKey(i)) == g_keyWearer))
                // do they belong to us?
            {
                if (llGetSubString(llDetectedName(i), 0, llStringLength(what2update) - 1) == what2update)
                    // now starts it with a name like ours?
                {  // so kill, kill, kill
                    llOwnerSay("There is more than one " + what2update + " rezzed from you. So this one got deleted. Please use only one updater.");
                    llDie();
                }
            }
        }
    }

    timer()
    {
        // now answer from another upder, so all is good, back to normal updating mode
        state updating;
    }
}
state updating
{
    state_entry()
    {
        initiate();
        // we need to listen to the double check channel as long as  we exists
        llListen(g_nDoubleCheckChannel,"",NULL_KEY,"");
    }

    on_rez(integer start_param)
    {
        llResetScript();
    }
    changed(integer change)
    {
        if(change & CHANGED_INVENTORY)
        {
            llResetScript();
        }
    }
    dataserver(key id, string data)
    {
        if (id == dataid)
        {
            if(data != EOF)
            {
                oldItemsToDelete += [data];
                line++;
                dataid = llGetNotecardLine(noteCard, line);
            }
            else
            {
                line = 0;
            }
        }
    }
    touch_start(integer num_detected)
    {
        if(llDetectedKey(0) == g_keyWearer)
        {
            llOwnerSay("Have me close to your " + what2update + " version >= " + llGetSubString((string)minVersion, 0, 4) + " and select in the " + what2update + " menu Update.");
        }
    }
    listen(integer channel, string name, key id, string message)
    {
        if (llGetOwnerKey(id) == g_keyWearer) //collar has to have the same owner as the updater!
        {
            if(channel == DEBUG_CHANNEL)
            {
                debugMessage = message;
            }
            else
            {
                debug(message);
                list temp = llParseString2List(message, ["|"], []);
                string command0 = llList2String(temp,0);
                string command1 = llList2String(temp,1);
                if (command0 == "UPDATE")
                {// Collar responded with update
                    if ((float)command1 >= minVersion && (float)command1 < version)
                    {//Collar version is at least 3.000 and lower than to Update version
                        llWhisper(updateChannel, "get ready");
                    }
                    else if ((float)command1 < (minVersion - 0.001) )
                    {
                        llWhisper(updateChannel, "nothing to update");
                        llOwnerSay("Your " + what2update + " is previous version "+llGetSubString((string)minVersion,0,4)+" and cannot be updated this way, please get " + what2update + " version " + llGetSubString((string)minVersion, 0, 4) + " or higher first.");
                        // the SubAO is outdated, so we try to get a newer version
                        if (g_szReplacementItemToRequest!="")
                        {
                            llOwnerSay("A new version of '" + g_szReplacementItemToRequest + "' will be requested now.");
                            string url = baseurl;
                            url += "object=" + llEscapeURL(g_szReplacementItemToRequest);
                            url += "&version=" + llEscapeURL(command1);
                            httprequest = llHTTPRequest(url, [HTTP_METHOD, "GET"], "");

                        }
                    }
                    else if ((float)command1 >= version)
                    {
                        llWhisper(updateChannel, "nothing to update");
                        llOwnerSay("Your " + what2update + " is the same or newer version, nothing to update.");
                    }
                }
                else if (command0 == "ready")
                {// Collar responed everything is ready, douplicate items were deleted so start to send stuff over
                    updatePin = (integer)command1;
                    CopyUpdateManager(id, updatePin);
                    // StartUpdate(id, (integer)command1, TRUE);
                }
                else if (message == "Manager Ready")
                {
                    if (llGetListLength(oldItemsToDelete) > 0 && llToLower(llList2String(oldItemsToDelete, 0)) != "nothing")
                    {
                        llWhisper(updateChannel, "delete," + llDumpList2String(oldItemsToDelete, ","));
                    }
                    else
                    {
                        OfferUpdate(id);
                    }
                }
                else if (message == "deletedOld")
                {
                    OfferUpdate(id); // give a list of item types to update
                }
                else if (command0 == "giveList")
                {// Collar requested a list of items of one type, send a list of item that will be copied
                    GiveItemList((integer)command1);
                }
                else if (message == "ready to receive")
                {
                    StartUpdate(id, updatePin);
                }
                else if (message == "copying child scripts")
                {
                    llSetText("PLEASE WAIT\nFinalizing Update\nCopying Scripts to Childprims", <1,0,0>, 1);
                }
                else if (message == "restarting collar scripts")
                {
                    llSetText("PLEASE WAIT\nFinalizing Update\nRestarting scripts.", <1,0,0>, 1);
                }
                else if (message == "finished")
                {// lets be sure also the update script is resetted before showing this
                    llSleep(2.0);
                    llSetLinkColor(3, <0,1,0>, ALL_SIDES);
                    llSetText("Update Finished.\nYour " + what2update + " has been updated to:\n" + llGetSubString((string)version, 0, 4), <0,1,0>, 1);
                    hoverText = "PLEASE WAIT\n";
                    llParticleSystem([]);
                    llDialog(g_keyWearer,"Update finished!\nYour " + what2update + " has been updated to:\n" + llGetSubString((string)version, 0, 4), ["Ok"],-47114711);
                    if(debugMessage != "")
                    {
                        llOwnerSay("You may have seen an error:\n \"" + debugMessage + "\".\nThis should be nothing to worry.");
                    }
                }
            }
        }
    }

    http_response(key request_id, integer status, list metadata, string body)
    {
        if (request_id == httprequest)
        {
            if (llGetListLength(llParseString2List(body, ["|"], [])) == 2)
            {
                llOwnerSay("The update should be delivered in about 30 seconds.");
                //client side is done now.  server has queued the delivery,
                //and in-world giver will send us our object when it next
                //pings the server
            }
            else if (body=="current")
            {
                llOwnerSay("The Update server reports your '"+g_szReplacementItemToRequest+"' as up to date. Maybe you are using a beta version?");
            }
            else if (llGetSubString(body,0,2) == "NSO")
            {
                llOwnerSay("The Update server could not find '"+llGetSubString(body,4,-1)+"'. Maybe you are using a beta version?");
            }
            else
                // answer from update server not recognized
            {
                llOwnerSay("Error while trying to request your '"+g_szReplacementItemToRequest+"': '"+body+"'. Please contact OpenCollar Support or get a new version at our HQ.");
            }
        }
    }

}